package example.runner3;

import java.awt.Point;
import java.io.File;

import logging.LogSystem;
import logging.Relationship;
import logging.Themes;
import materials.network.NetworkInterface;

import service.genetic.NetworkGeneticService;
import service.network.NetworkFilesystemService;
import visual.ValuePerTime;
import visual.VisualMatrix;

/**
 * Klasse, die alles koordiniert
 * 
 * Das Prinzip ist, dass die Runner genau wie die Fische im Beispiel von Sheepy
 * so viel Essen wie möglich zu sich nehmen. Als Input sehen sie je zwei Felder
 * in jede Richtung. Dabei ist es den Runnern nicht möglich Wände von anderen
 * Runnern unterscheiden, sie können nur zwischen leer,essen und versperrt
 * entscheiden.
 * 
 * xxxxx xxxxx xxrxx xxxxx xxxxx
 * 
 * Die Runner können in alle Richtungen laufen.
 * 
 * hier werden die Runner erstellt und die Evolution durchgeführt. TODO c++
 * lernen
 * 
 * @author Ramox
 * 
 */
public class Game {
	public static final int FIELD_HEIGHT = 100; // höhe des Feldes in kästchen
	public static final int FIELD_WIDTH = 100; // breite des feldes in kästchen
	// anzahl der runner, min 5 und nur zahlen wie 3n+2
	public static final int RUNNER_COUNT = 40;
	// anzahl der maximalen schritte der runner vor der evolution
	public static final int ROUNDS_UNTIL_EVOLUTION = 200;
	public static int FOOD_COUNT = (int) (0.05*(FIELD_HEIGHT-2)*(FIELD_WIDTH-2)-RUNNER_COUNT); // anzahl des essens auf dem Feld
	private static int Runden = 1;
	// Das Feld ist so aufgebaut, dass
	// die Runner als id+1 dargestellt werden
	// die leeren stellen als 0
	// das essen als -2
	// und die Wände als -1
	private int[][] _field;

	private Runner[] _runner;
	private ControlField _cf;
	private ValuePerTime _vpt;
	private ValuePerTime _vpt2;
	private VisualMatrix _vm;
	LogSystem _ls;

	/**
	 * Erstellt das Feld und die erste Generation von Runner, sowie das erste
	 * Essen.
	 * 
	 * TODO:gui-Verknüpfung sollte mit Beobachter-Muster realisiert werden
	 * 
	 * @param cf
	 *            gui-Komponente, damit sie aktualisiert wird
	 */
	public Game(ControlField cf) {
		_cf = cf;
		_vpt = new ValuePerTime();
		_vpt2 = new ValuePerTime();
		_vpt.setVisible(true);
		_vpt2.setVisible(true);
		_vpt.setAutoScale(true);
		_vpt2.setAutoScale(true);
		_vm = new VisualMatrix();
		_vm.setSize(5);
		_ls = new LogSystem("/home/sheepy/Desktop/", "Neues Testverzeichnis");
		_ls.addThemeFolder(Themes.Relationship);
		createField();
		createRunners();
		createFood();
	}

	int FOOD = 0;
	double average = 0;

	/**
	 * erstellt das Essen und platziert es
	 */
	private void createFood() {
		for (int i = 0; i < FOOD_COUNT; i++) {
			newFood();
		}
	}

	/**
	 * erstellt ein neues Essen, das zufällig platziert wird.
	 */
	private void newFood() {
		FOOD++;
		int x, y;
		boolean done = false;
		do {
			x = (int) (Math.random() * FIELD_WIDTH);
			y = (int) (Math.random() * FIELD_HEIGHT);

			if (_field[x][y] == 0) {
				_field[x][y] = -2;
				done = true;

			}
		} while (!done);
	}

	/**
	 * gibt das feld zurück
	 * 
	 * @return feld
	 */
	public int[][] getField() {
		return _field;
	}

	int coooo = 0;

	/**
	 * Startet das Spiel
	 * 
	 * Es gibt eine Endlosschleife, die immer wieder neue Generationen erzeugt.
	 * Ausgegeben wird die Nummer der Generation und die Fitness des besten
	 * runners.
	 *  
	 */
	int z = 0;
	public void startGame() {
		int count = 0;
		do {
			for (int i = 0; i < ROUNDS_UNTIL_EVOLUTION; i++) {
				moveRunners();
				// if (count>=ZEIGE_ALLE){
				_cf.repaint();
				// count = 0;
				// }
			}

			average += (FOOD - FOOD_COUNT);
			_vpt2.addData((int) ((FOOD - FOOD_COUNT)));

			System.out.println("Generation: " + count + " Food: "
					+ (FOOD - FOOD_COUNT) + " ");

			FOOD = 0;
			z++;
			if(z % Runden == 0){
				evolution();
				z = 0;
			}
			
			createField();
			for (int i = 0; i < RUNNER_COUNT; i++) {
				setRunnerPosition(i + 1);
			}
			createFood();

			count++;
		} while (1 == 1); // TODO: endlosschleife!!!
	}

	/**
	 * Erstellt die neue Generation
	 */
	private void evolution() {
		// sortiert die runner nach ihrer fitness
		for (int j = 0; j < RUNNER_COUNT; j++) {
			for (int i = j; i < RUNNER_COUNT; i++) {
				if (_runner[i].getFitness() > _runner[j].getFitness()) {
					Runner r = _runner[i];
					_runner[i] = _runner[j];
					_runner[j] = r;
				}
			}
		}
		_vm.updateGUI(_runner[0].getNet());
		_vpt.addData((int) _runner[0].getFitness());
			NetworkGeneticService ngs = new NetworkGeneticService();
		coooo++;
		Runner[] runner = new Runner[RUNNER_COUNT];

		int i = 0;
		while (i < RUNNER_COUNT) {
			runner[i] = new Runner(ngs.recombineNetworks(getParent(),
					getParent()));
			ngs.mutateNetworkRandom(runner[i].getNet(), 0.000001, 1);
			ngs.mutateNetwork(runner[i].getNet(), 0.002, 0.1);
			i++;
		}
		_runner = runner;
	}

	private NetworkInterface getParent() {

		Runner[] runner = _runner.clone();

		for (int j = 0; j < RUNNER_COUNT; j++) {
			for (int i = j; i < RUNNER_COUNT; i++) {
				if (runner[i].getFitness() > runner[j].getFitness()) {
					Runner r = runner[i];
					runner[i] = runner[j];
					runner[j] = r;
				}
			}
		}

		double rnd = Math.random() * 100;

		double[] bereiche = new double[RUNNER_COUNT];

		double rest = 100;
		for (int i = 0; i < RUNNER_COUNT; i++) {
			bereiche[i] = 100 - rest + rest / 2;
			rest = bereiche[i];
		}

		for (int i = 0; i < RUNNER_COUNT; i++) {
			if (rnd < bereiche[i]) {
				return runner[i].getNet();
			}
		}
		return runner[0].getNet();
	}

	/**
	 * bewegt alle Runner
	 */
	private void moveRunners() {
		askSpeed();
		for (int i = 0; i < RUNNER_COUNT; i++) {
			Point pos = getRunnerPosition(i);
			Direction dir = _runner[i].getNextDirection(getUmgebung(pos));
			moveRunner(i, dir);
		}
	}

	int speed_verz = 0;

	public void setSpeedVerz(int i) {
		speed_verz = i;
	}

	private void askSpeed() {
		if (speed_verz > 0) {
			try {
				synchronized (this) {
					this.wait(speed_verz);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * bewegt einen Runner mit der id id in die Richtung dir, wenn es möglich
	 * ist
	 * 
	 * @param id
	 *            id des zu bewegenden runner
	 * @param dir
	 *            Richtung der bewegung
	 */
	private void moveRunner(int id, Direction dir) {
		Point pos = getRunnerPosition(id);
		int x = pos.x;
		int y = pos.y;

		if (dir == Direction.NorthWest) {
			x -= 1;
			y -= 1;
		}
		if (dir == Direction.North) {
			y -= 1;
		}
		if (dir == Direction.NorthEast) {
			x += 1;
			y -= 1;
		}
		if (dir == Direction.East) {
			x += 1;
		}
		if (dir == Direction.SouthEast) {
			x += 1;
			y += 1;
		}
		if (dir == Direction.South) {
			y += 1;
		}
		if (dir == Direction.SouthWest) {
			x -= 1;
			y += 1;
		}
		if (dir == Direction.West) {
			x -= 1;
		}

		if (_field[x][y] == 0) // leer
		{
			_field[pos.x][pos.y] = 0;
			_field[x][y] = id + 1;
		} else if (_field[x][y] == -2) // essen
		{
			_field[pos.x][pos.y] = 0;
			_field[x][y] = id + 1;
			_runner[id].increaseHealth();
			newFood();
		} else // wand oder anderer Runner
		{
			_runner[id].decreaseHealth();
		}

	}

	/**
	 * gibt die umgebung der position pos wieder.
	 * 
	 * xxxxx xxxxx xxpxx xxxxx xxxxx
	 * 
	 * @param pos
	 *            Position, zu der die Umgebung gesucht ist
	 * @return Umgebung (5x5) um die position
	 */
	private int[] getUmgebung(Point pos) {
		int[] umgebung = new int[25];

		pos.x -= 2;
		pos.y -= 2;

		for (int i = 0; i < 5; i++) {
			for (int j = 0; j < 5; j++) {
				if ((pos.x + j >= 0)
						&& (pos.y + i >= 0 && (pos.x + j < FIELD_WIDTH) && (pos.y
								+ i < FIELD_HEIGHT))
						&& (pos.x + j <= FIELD_WIDTH)
						&& pos.y + i <= FIELD_WIDTH) {
					umgebung[i * 5 + j] = _field[pos.x + j][pos.y + i];
				} else {
					if ((pos.x + j < 0) && (pos.y + i >= 0)
							&& (pos.y + i < FIELD_HEIGHT)) {
						umgebung[i * 5 + j] = _field[FIELD_WIDTH + pos.x + j][pos.y
								+ i];
					} else if ((pos.x + j >= FIELD_WIDTH) && (pos.y + i >= 0)
							&& (pos.y + i < FIELD_HEIGHT)) {
						umgebung[i * 5 + j] = _field[pos.x + j - FIELD_WIDTH][pos.y
								+ i];
					} else if ((pos.y + i < 0) && (pos.x + j >= 0)
							&& (pos.x + j < FIELD_WIDTH)) {
						umgebung[i * 5 + j] = _field[pos.x + j][FIELD_HEIGHT
								+ pos.y + i];
					} else if ((pos.y + i >= FIELD_HEIGHT) && (pos.x + j >= 0)
							&& (pos.x + j < FIELD_WIDTH)) {
						umgebung[i * 5 + j] = _field[pos.x + j][pos.y + i
								- FIELD_HEIGHT];
					} else if ((pos.y + i >= FIELD_HEIGHT) && (pos.x + j < 0)) {
						umgebung[i * 5 + j] = _field[FIELD_WIDTH + pos.x + j][pos.y
								+ i - FIELD_HEIGHT];
					} else if ((pos.y + i >= FIELD_HEIGHT)
							&& (pos.x + j >= FIELD_WIDTH)) {
						umgebung[i * 5 + j] = _field[pos.x + j - FIELD_WIDTH][pos.y
								+ i - FIELD_HEIGHT];
					} else if ((pos.y + i < 0) && (pos.x + j >= FIELD_WIDTH)) {
						umgebung[i * 5 + j] = _field[pos.x + j - FIELD_WIDTH][pos.y
								+ i + FIELD_HEIGHT];
					} else if ((pos.y + i < 0) && (pos.x + j < 0)) {
						umgebung[i * 5 + j] = _field[pos.x + j + FIELD_WIDTH][pos.y
								+ i + FIELD_HEIGHT];
					} else {
						umgebung[i * 5 + j] = -1;
					}
				}
			}
		}

		return umgebung;
	}

	/**
	 * holt die Position des Runners aus dem Feld
	 * 
	 * @param id
	 *            id des Runners, von dem die position gesucht ist
	 * 
	 * @return Position des Runners
	 */
	private Point getRunnerPosition(int id) {
		// nicht schön, weil er duch das komplette Feld gehen muss
		for (int i = 0; i < FIELD_WIDTH; i++) {
			for (int j = 0; j < FIELD_HEIGHT; j++) {
				if (_field[i][j] == id + 1) {
					return new Point(i, j);
				}
			}
		}
		return null;
	}

	/**
	 * erzeugt die erste Generation von Runnern
	 */
	private void createRunners() {
		_runner = new Runner[RUNNER_COUNT];

		for (int i = 0; i < RUNNER_COUNT; i++) {
			_runner[i] = new Runner(rn.getNet());
			NetworkGeneticService ngs = new NetworkGeneticService();
			ngs.mutateNetwork(_runner[i].getNet(), 50, 1);
			setRunnerPosition(i + 1);
		}
	}

	RunnerNetz rn = new RunnerNetz();

	/**
	 * Setzt den Runner mit der id (idPlus1-1) zufällig auf das feld
	 * 
	 * @param idPlus1
	 *            id des Runners + 1, weil die id auch 0 sein könnte
	 */
	private void setRunnerPosition(int idPlus1) {
		int x;
		int y;

		do {
			x = (int) (Math.random() * FIELD_WIDTH);
			y = (int) (Math.random() * FIELD_HEIGHT);

			if (_field[x][y] == 0) {
				_field[x][y] = idPlus1;
			}
		} while (_field[x][y] != idPlus1);
	}

	/**
	 * erzeugt das Feld und die Wände
	 */
	private void createField() {
		_field = new int[FIELD_WIDTH][FIELD_HEIGHT];

		// Wände
		for (int i = 0; i < FIELD_WIDTH; i++) {
			_field[i][0] = -1;
			_field[i][FIELD_HEIGHT - 1] = -1;
		}
		for (int i = 0; i < FIELD_HEIGHT; i++) {
			_field[0][i] = -1;
			_field[FIELD_WIDTH - 1][i] = -1;
		}

		// for (int j = 1; j < FIELD_HEIGHT / 10; j++) {
		// for (int i = 25; i < FIELD_WIDTH-25; i++) {
		// _field[i][j * 10] = -1;
		// }
		// }

//		for (int i = 0; i < FIELD_HEIGHT; i++) {
//			for (int j = 0; j < FIELD_WIDTH; j++) {
//				if (i%7 == 0 && j %10 == 0) {
//					_field[j][i] = -1;
//				}
//			}
//		}
	}
}
